package org.flashamaphone.tones
{
	import flash.utils.ByteArray;
	
	import org.flashamaphone.debug.Debugger;
	import org.flashamaphone.voices.IVoice;

	public class Polytone extends Tone implements ITone
	{
		/** tones */
		protected var _tones:Vector.<ITone>;
		
		/** frequencies */
		protected var _frequencies:Array;
		
		/**
		 * frequency getter
		 * 
		 * @return frequency array
		 */
		public function get frequencies():Array {
			return _frequencies;
		}
		
		/**
		 * frequencies setter
		 * 
		 * @param frequency array
		 */
		public function set frequencies(value:Array):void {
			_tones = new Vector.<ITone>();
			for each ( var freq:Number in value ) {
				var tone:ITone = super.clone();
				tone.frequency = freq;
				_tones.push(tone);
			}
			_frequencies = value;
		}
		
		/** 
		 * constructor
		 */
		public function Polytone() {}
		
		/**
		 * voice setter
		 * 
		 * @param voice
		 */
		public override function set voice(value:IVoice):void {
			for each ( var tone:ITone in _tones) {
				tone.voice = value;
			}
			super.voice = value;
		}
		
		/** 
		 * get press bytes
		 * 
		 * @return bytearray
		 */
		public override function get press():ByteArray {
			var returnBytes:ByteArray = phaseMix(Tone.PRESS_PHASE, _tones);
			return returnBytes;
		}
		
		/** 
		 * get hold bytes
		 * 
		 * @return bytearray
		 */
		public override function get hold():ByteArray {
			var returnBytes:ByteArray = phaseMix(Tone.HOLD_PHASE, _tones);
			return returnBytes;
		}
		
		/** 
		 * get unpress bytes
		 * 
		 * @return bytearray
		 */
		public override function get unpress():ByteArray {
			var returnBytes:ByteArray = phaseMix(Tone.UNPRESS_PHASE, _tones);
			return returnBytes;
		}
		
		/**
		 * phase setter
		 * 
		 * @param phase
		 */
		public override function set phase(value:String):void {
			for each ( var tone:ITone in _tones) {
				tone.phase = value;
			}
			super.phase = value;
		}
		
		/**
		 * get live bytestream x bytes at a time depending on phase
		 * 
		 * @param number of bytes to get
		 * @return bytes
		 */
		public override function stream(numBytes:int):ByteArray {
			var returnBytes:ByteArray = streamMix(_tones, numBytes/8);
			if(debug) {
				Debugger.debug(returnBytes);
			}
			return returnBytes;
		}
		
		/**
		 * reset byte stream of this note
		 */
		public override function resetByteStream():void {
			for each ( var tone:ITone in _tones) {
				tone.resetByteStream();
			}
			super.resetByteStream();
		}
		
		/**
		 * clone
		 * 
		 * @return toned clone
		 */
		public override function clone():ITone {
			var clone:ITone = super.clone();
			(clone as Polytone).frequencies = this.frequencies;
			return clone;
		}
		
		/**
		 * mix tones
		 * 
		 * @param tones (ITone vector)
		 * @param number of samples
		 * @return mixed bytearray
		 */
		protected function streamMix(tones:Vector.<ITone>, numSamples:int):ByteArray {
			var numBytes:int = numSamples*8;
			var bytesList:Vector.<ByteArray> = new Vector.<ByteArray>();
			
			// get bytes for all keys for the number of samples desired
			for each (var tone:ITone in tones) {
				var sound:ByteArray = tone.stream(numBytes);				
				sound.position = 0;
				bytesList.push(sound);
				this.phase = tone.phase;
			}
			
			// break out each sample, add them up, and re-write byte array
			var mix:ByteArray = new ByteArray();
			for (var c:int = 0; c < numSamples; c++) {
				var combinedSample:Number = 0;
				var samplesCounted:int = 0;
				for each (var bytes:ByteArray in bytesList) {
					if (bytes.bytesAvailable >= 8) {
						var left:Number = bytes.readFloat(); // left
						var right:Number = bytes.readFloat(); // right
						combinedSample += (left + right)/2;
						samplesCounted ++;
					}
				}
				
				if (samplesCounted > 0) {
					mix.writeFloat(combinedSample/samplesCounted); // left
					mix.writeFloat(combinedSample/samplesCounted); // right
				}
			}
			return mix;
		}
		
		/**
		 * mix tones
		 * 
		 * @param phase
		 * @param tones (ITone vector)
		 * @return mixed bytearray
		 */
		protected function phaseMix(phase:String, tones:Vector.<ITone>):ByteArray {
			var bytesList:Vector.<ByteArray> = new Vector.<ByteArray>();
			
			// get bytes for all keys for the number of samples desired
			for each (var tone:ITone in tones) {
				var sound:ByteArray;
				switch( phase ) {
					case Tone.PRESS_PHASE: sound = tone.press; break;
					case Tone.HOLD_PHASE: sound = tone.hold; break;
					case Tone.UNPRESS_PHASE: sound = tone.unpress; break;
				}
				
				var numSamples:int = sound.length/8;
				sound.position = 0;
				bytesList.push(sound);
			}
			
			// break out each sample, add them up, and re-write byte array
			var mix:ByteArray = new ByteArray();
			for (var c:int = 0; c < numSamples; c++) {
				var combinedSample:Number = 0;
				var samplesCounted:int = 0;
				for each (var bytes:ByteArray in bytesList) {
					if (bytes.bytesAvailable >= 8) {
						var left:Number = bytes.readFloat(); // left
						var right:Number = bytes.readFloat(); // right
						combinedSample += (left + right)/2;
						samplesCounted ++;
					}
				}
				
				if (samplesCounted > 0) {
					mix.writeFloat(combinedSample/samplesCounted); // left
					mix.writeFloat(combinedSample/samplesCounted); // right
				}
			}
			return mix;
		}
	}
}